﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;
using Common.DataTypes;


namespace LINQAmazonProvider
{
    public class AmazonExpressionVisitor 
    {
        private Dictionary<string, string> _searchParameters;
        private bool descending = false;
        public Dictionary<string,string> ProcessExpression(Expression expression)
        {
            _searchParameters = new Dictionary<string,string>();
            _searchParameters.Add("Method",null);
            VisitExpression(expression);
            return _searchParameters;
        }

        protected virtual void VisitExpression(Expression expression)
        {
            if (expression.NodeType == ExpressionType.AndAlso)
            {
                VisitAndAlso((BinaryExpression)expression);
            }
            else if (expression.NodeType == ExpressionType.Equal)
            {
                VisitEqual((BinaryExpression)expression);
            }
            else if (expression.NodeType == ExpressionType.LessThanOrEqual)
            {
                VisitLessThanOrEqual((BinaryExpression)expression);
            }
            else if (expression.NodeType == ExpressionType.LessThan)
            {
                VisitLessThan((BinaryExpression)expression);
            }
            else if (expression.NodeType == ExpressionType.GreaterThanOrEqual)
            {
                VisitGreaterThanOrEqual((BinaryExpression)expression);
            }
            else if (expression.NodeType == ExpressionType.GreaterThan)
            {
                VisitGreaterThan((BinaryExpression)expression);
            }
            else if (expression is MethodCallExpression)
            {
                VisitMethodCall((MethodCallExpression)expression);
            }
            else if (expression is LambdaExpression)
            {
                VisitExpression(((LambdaExpression)expression).Body);
            }
            else if (expression is MemberExpression)
            { 
                //TODO
                VisitMemberExpression((MemberExpression)expression, descending);
            }
        }

        private void VisitMemberExpression(MemberExpression memberExpression, bool descending)
        {
            
            string sortParameter = null;
            int i = 1;
            bool end = false;
            string orderByKey = "Sort.";  
            switch (memberExpression.Member.Name){

                case  "Title":
                    sortParameter="titlerank";
                    break;
                case "ListPrice":
                    if (!descending)
                    {
                        sortParameter="pricerank";
                    }
                    else
                    {
                        sortParameter="inverse-pricerank";
                    }
                    break;
                case "PublicationDate":
                    sortParameter="daterank";
                    break;
                default:
                    throw new NotSupportedException("Key to Order not supported");
                    

           }
            
            while(!end){
                if (_searchParameters.ContainsKey(orderByKey+i)){                   
                    i++;                  
                }
                else{
                        _searchParameters.Add(orderByKey+i, sortParameter);
                        end=true;
                    }
            }

            // Change sort order to get it back to normal
            while (i > 1) {
                _searchParameters[orderByKey + i] = _searchParameters[orderByKey + (i - 1)];
                i--;
            }
            _searchParameters[orderByKey + 1] = sortParameter;

            descending = false;
        }

        private void VisitAndAlso(BinaryExpression andAlso)
        {
            VisitExpression(andAlso.Left);
            VisitExpression(andAlso.Right);
        }

        private void VisitEqual(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess) 
            {
                parameterName = ((MemberExpression)expression.Left).Member.Name;

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = (string)((ConstantExpression)expression.Right).Value;
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitLessThanOrEqual(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MaximumPrice";

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = (((ConstantExpression)expression.Right).Value).ToString();
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitLessThan(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MaximumPrice";
                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = ((int)((ConstantExpression)expression.Right).Value - 1).ToString();
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitGreaterThanOrEqual(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MinimumPrice";

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = (((ConstantExpression)expression.Right).Value).ToString();
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitGreaterThan(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MinimumPrice";

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = ((int)((ConstantExpression)expression.Right).Value + 1).ToString();
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }


        private void VisitMethodCall(MethodCallExpression expression)
        {

            if ((expression.Method.DeclaringType == typeof(Queryable)) &&
              (expression.Method.Name == "Where"))
            {
                VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
            }
            else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
                ((expression.Method.Name == "OrderBy") || (expression.Method.Name == "ThenBy")))
            {
                VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
                VisitExpression(expression.Arguments[0]);
                
            }
            else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
                ((expression.Method.Name == "ThenByDescending") || (expression.Method.Name == "OrderByDescending")))
            {
                descending = true;
                VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
                VisitExpression(expression.Arguments[0]);  
              

            }
            else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
                (expression.Method.Name == "Count"))
            {

                _searchParameters["Method"]="Count";

                VisitExpression(expression.Arguments[0]);  
            }
            else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
                (expression.Method.Name == "First"))
            {
                _searchParameters["Method"]="First";
                VisitExpression(expression.Arguments[0]);
            }
            else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
                (expression.Method.Name == "Last"))
            {
                _searchParameters["Method"] = "Last";
                VisitExpression(expression.Arguments[0]);
            }
            else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
                (expression.Method.Name == "Skip"))
            {
                //_searchParameters["Method"] = "Skip";
                _searchParameters.Add("Skip", expression.Arguments[1].ToString());
                VisitExpression(expression.Arguments[0]);

            }
            else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
                (expression.Method.Name == "Take"))
            {
                _searchParameters.Add("Take", expression.Arguments[1].ToString());
                VisitExpression(expression.Arguments[0]);

            }
            else if ((expression.Method.DeclaringType == typeof(String)) &&
              (expression.Method.Name == "Contains"))
            {
                if (expression.Object.NodeType == ExpressionType.MemberAccess)
                {
                    MemberExpression memberExpr = (MemberExpression)expression.Object;
                    if (memberExpr.Expression.Type == typeof(Book))
                    {
                        Expression argument;

                        argument = expression.Arguments[0];
                        if (argument.NodeType == ExpressionType.Constant)
                        {
                            _searchParameters.Add(memberExpr.Member.Name, (String)((ConstantExpression)argument).Value);
                        }
                        else if (argument.NodeType == ExpressionType.MemberAccess)
                        {
                            _searchParameters.Add(memberExpr.Member.Name, (String)GetMemberValue((MemberExpression)argument));
                        }
                        else
                        {
                            throw new NotSupportedException("Expression type not supported: " + argument.NodeType.ToString());
                        }
                    }
                }
            }
            else
            {
                throw new NotSupportedException("Method not supported: " + expression.Method.Name);
            }
        }

        #region Helpers
        private Object GetMemberValue(MemberExpression memberExpression)
        {
            MemberInfo memberInfo;
            Object obj;
            
            LambdaExpression lambda = Expression.Lambda(memberExpression);
            Delegate fn = lambda.Compile();
            object veamos = fn.DynamicInvoke(null);

            if (memberExpression == null)
                throw new ArgumentNullException("memberExpression");

            return fn.DynamicInvoke(null);

            //// Get object
            //if (memberExpression.Expression is ConstantExpression)
            //    obj = ((ConstantExpression)memberExpression.Expression).Value;
            //else if (memberExpression.Expression is MemberExpression)
            //    obj = GetMemberValue((MemberExpression)memberExpression.Expression);
            //else
            //    throw new NotSupportedException("Expression type not supported: " + memberExpression.Expression.GetType().FullName);

            //// Get value
            //memberInfo = memberExpression.Member;
            //if (memberInfo is PropertyInfo)
            //{
            //    PropertyInfo property = (PropertyInfo)memberInfo;
            //    return property.GetValue(obj, null);
            //}
            //else if (memberInfo is FieldInfo)
            //{
            //    //FieldInfo field = (FieldInfo)memberInfo;
            //    //return field.GetValue(obj);
            //    return fn.DynamicInvoke(null);
            //}
            //else
            //{
            //    throw new NotSupportedException("MemberInfo type not supported: " + memberInfo.GetType().FullName);
            //}
        }

        #endregion Helpers
    }
}
